<?php
/**
 * Contains business logic related to seminar and registrations,
 * shared by several controller classes.
 * @author jan
 */
class SeminarService extends ServiceBase {
	
	public function __construct() {
		parent::__construct();
	}
	
	/**
	 * Confirms one or more registrations of one person and one saminar.
	 * @param $registrationList registration or a list of registrations
	 */
	public function confirmRegistration($registrationList, $orgNotes=null) {
		if (count($registrationList) == 0) {
			return;
		}
		if (! is_array($registrationList[0])) {
			$registrationList = array($registrationList);
		}
		
		$idList = array();
		foreach ($registrationList as $registration) {
			if ($registration['confirmed_at']) {
				continue;
			}
			
			$idList[] = $registration['id'];
			
			if ($orgNotes) {
				$registration['org_notes'] = $orgNotes;
			}
			$registration['price_paid'] = $registration['total_price'];
			$registration['confirmed_at'] = time();
			$registration['confirmed_by_id'] = $this->authUser['id'];
			$this->dao->update('registration', $registration);
		}
		if (count($idList) == 0) {
			return;
		}
		
		$idsAsString = join(', ', $idList);
		
		// FIXME this should select the seminar in the language preferred by the person
		$seminar = $this->dao->fetchSeminar($registrationList[0]['seminar_id'], $this->getL10nLanguageCode());
		$person = $this->dao->getById('person', $registrationList[0]['person_id']);
		
		// Send an email to the user, that the payment for his registration was confirmed
		if ($person['email']) {
			$mailBody = text::get('registration-confirmationEmail-body', $idsAsString, $seminar['name']);
			$mailResult = email::sendInUtf8(
					$person['first_name'] . ' ' . $person['last_name'],
					$person['email'],
					text::get('registration-confirmationEmail-title', $idsAsString),
					email::addHeadingAndClosing($mailBody, $person));
			if ($mailResult == email::$SUCCESS) {
				// TODO message saying that the email was sent.
				// $this->msg->info('');
			}
		}
		
		$this->msg->info('registration.confirm-success', $idsAsString, format::personFullName($person, false));
	}
	
	private function computeCacellationFee($registration, $seminar, $cancellationCharge) {
		$fee = 0;
		// Cancellation fee if the registration deadline has passed
		if ($cancellationCharge && date::isInPast($seminar['registration_deadline'])) {
			$fee = ($registration['total_price'] * $cancellationCharge['percentage_after_deadline']) / 100;
		}
		return $fee;
	}
	
	/**
	 * Cancels the registration with the given ID.
	 * Doesn't matter who is cancelling it (user, organization or admin).
	 * 
	 * @param $reason must be not empty if the user does is not cancelling his own registration.
	 * @throws LogicException in case of something goes wrong
	 * @return registration object
	 */
	public function cancelRegistration($registrationId, $reason) {
		$registration = $this->dao->getById('registration', $registrationId);
		if (! $registration) {
			if (valid::isWhite($reason)) {
				throw new InvalidArgumentException(text::get('error-recordNotFound'));
			}
		}
		
		// FIXME this should select the seminar in the language preferred by the person
		$seminar = $this->dao->fetchSeminar($registration['seminar_id'], $this->getL10nLanguageCode());
		$paymentAcceptance = $this->dao->getPaymentAcceptance($seminar['id'], $registration['payment_organization_id']);
		if (! registrationUtils::canBeCancelled($registration, $seminar, $paymentAcceptance)) {
			throw new LogicException(text::get('registration-error-cannotBeCancelled'));
		}
		
		if ($registration['person_id'] != $this->sessionData->getAuthUser('id')) {
			if (valid::isWhite($reason)) {
				throw new InvalidArgumentException(text::get('error-required', text::get('registration-cancellation_reason')));
			}
		}
		
		// Find out who cancelled the registration
		$whoCancelledIt = UserRole::getUserRole();

		// TODO verify, that the user is authorized to cancel this registration
		if ($whoCancelledIt == UserRole::$user) {
		}
		
		$registration['cancelled_at'] = time();
		$registration['cancelled_by_id'] = $this->sessionData->getAuthUser('id');
		$registration['cancellation_reason'] = $reason;
	
		$priceGroup = $this->dao->getById('price_group', $registration['price_group_id']);
		$cancellationCharge = $this->dao->getSeminarCancellationCharge($seminar['id']);
	
		$registration['cancellation_fee'] = $this->computeCacellationFee($registration, $seminar, $cancellationCharge);
		if ($registration['cancellation_fee']) {
			$this->msg->warn(text::get('registration-cancellationFeeApplied', $cancellationFee . $priceGroup['currency_code']));
		}
	
		$this->dao->update('registration', $registration);
	
		$person = $this->dao->getById('person', $registration['person_id']);
		$organization = $this->dao->getOrganization($registration['payment_organization_id']);

		// Set the success message 
		if ($whoCancelledIt == UserRole::$user) {
			$this->msg->info(
					'registration-cancelled-byUser-success',
					$registrationId,
					$seminar['name']);
		}
		else {
			$this->msg->info(
					'registration-cancelled-byOrgOrAdmin-success',
					$registrationId,
					format::personFullName($person), $seminar['name']);
		}
		
		// Send an email to the person
		if ($whoCancelledIt != UserRole::$user && $person['email']) {
			
			$introArgs = array();
			if ($whoCancelledIt == 'Org') {
				$introArgs = array($organization['name']);
			}
			$emailForUserIntro = text::get('registration-cancelled-by' . ucfirst($whoCancelledIt) . '-emailForUser-intro', $introArgs);
			$emailForUserTitle = text::get('registration-cancelled-emailForUser-subject', $registrationId);
			$emailForUserBody = text::get(
					'registration-cancelled-emailForUser-body',
					$registration['id'],
					$seminar['name'],
					$emailForUserIntro,
					$this->sessionData->getAuthOrg('name'),
					$reason
			);
			
			$mailResult = email::sendInUtf8(
					format::personFullName($person),
					$person['email'],
					$emailForUserTitle,
					email::addHeadingAndClosing($emailForUserBody, $person)
			);
			if ($mailResult == email::$SUCCESS) {
				$this->msg->info('registration-cancelled-emailForUser-success');
			}
		}
		
		// Send an email to the organization
		if ($whoCancelledIt != UserRole::$org && $organization['emails_enabled'] && $organization['email']) {
			
			$fullName = format::personFullName($person);
			$confirmed = format::boolean($registration['confirmed_at'] == NULL);
			
			$emailForOrgIntro = text::get('registration-cancelled-by' . ucfirst($whoCancelledIt) . '-emailForOrg-intro');
			$emailForOrgTitle = text::get('registration-cancelled-emailForOrg-subject', $fullName);
			$emailForOrgBody = text::get(
					'registration-cancelled-emailForOrg-body',
					$emailForOrgIntro,
					$fullName,
					$seminar['name'],
					$registration['id'],
					$confirmed,
					format::price($registration['total_price'], $priceGroup['currency_code']),
					text::get('registration-paymentType-' . $registration['payment_type']),
					$reason);
			
			$mailResult = email::sendInUtf8(
					format::personFullName($person),
					$organization['email'],
					$emailForOrgTitle,
					$emailForOrgBody
			);
			if ($mailResult == email::$SUCCESS) {
				// Don't tell user about emails to organizations. He doesn't care.
				if ($whoCancelledIt == UserRole::$admin) {
					$this->msg->info('registration-cancelled-emailForOrg-success');
				}
			}
		}
		
		return $registration;
	}
	
	/**
	 * Loads all data required to display a registration.
	 */
	public function loadRegistrationDisplayData($registrationId) {
		$lang = $this->getL10nLanguageCode();
	
		if (! valid::isId($registrationId)) {
			$this->msg->error('error.invalidId');
			return false;
		}
	
		// Registration
		$registration = $this->dao->getById('registration', $registrationId);
		if (! $registration) {
			$this->msg->error('error.recordNotFound');
			return false;
		}
	
		// Seminar
		$registration['seminar'] = $this->dao->fetchSeminar($registration['seminar_id'], $lang);
	
		// Batch
		$registration['batch'] = $this->dao->getLocalizedBatch($registration['seminar_id'], $registration['batch_code'], $lang);
		$registration['batch']['batch_type'] = $this->dao->getLocalizedEntityByCode('batch_type', $registration['batch']['batch_type_code'], $lang);
	
		// Category and room
		$registration['category'] = $this->dao->getCategory($registration['seminar_id'], $registration['category_code']);
		$registration['category']['price'] = $this->dao->getCategoryPrice($registration['category']['category_code'], $registration['price_group_id']);
		if ($registration['room_code']) {
			$registration['room'] = $this->dao->getLocalizedEntityByCode('room', $registration['room_code'], $lang);
		}
	
		// Associated objects of category
		$registration['category']['program_type'] = $this->dao->getLocalizedEntityByCode('program_type', $registration['category']['program_type_code'], $lang);
		$registration['category']['accomodation_type'] = $this->dao->getLocalizedEntityByCode('accomodation_type', $registration['category']['accomodation_type_code'], $lang);
	
		// Exercise group and karmayoga type
		if ($registration['exercise_group_code']) {
			$registration['exerciseGroup'] = $this->dao->getLocalizedEntityByCode(
					'exercise_group', $registration['exercise_group_code'], $lang);
		}
		if ($registration['preferred_karmayoga_type_code']) {
			$registration['preferred_karmayoga_type'] = $this->dao->getLocalizedEntityByCode(
					'karmayoga_type', $registration['preferred_karmayoga_type_code'], $lang);
		}
		if ($registration['assigned_karmayoga_type_code']) {
			$registration['assigned_karmayoga_type'] = $this->dao->getLocalizedEntityByCode(
					'karmayoga_type', $registration['assigned_karmayoga_type_code'], $lang);
		}
	
		// Discounts and charges
		$registration['social_discount'] = $this->dao->getSeminarSocialDiscount($registration['seminar_id']);
		$registration['early_discount'] = $this->dao->getSeminarEarlyDiscount($registration['seminar_id']);
		$registration['late_charge'] = $this->dao->getSeminarLateCharge($registration['seminar_id']);
	
		// Payment data
		$registration['payment_organization'] = $this->dao->getById('organization', $registration['payment_organization_id']);
		$registration['payment_country'] = $this->dao->getByCode('country', $registration['payment_organization']['country_code']);
		$registration['price_group'] = $this->dao->getById('price_group', $registration['price_group_id']);;
		$registration['payment_acceptance'] = $this->dao->getPaymentAcceptance($registration['seminar_id'], $registration['payment_organization_id']);
	
		// Persons
		$registration['person'] = $this->dao->getById('person', $registration['person_id']);
		if ($registration['confirmed_by_id']) {
			$registration['confirmed_by'] = $this->dao->getById('person', $registration['confirmed_by_id']);
		}
		if ($registration['cancelled_by_id']) {
			$registration['cancelled_by'] = $this->dao->getById('person', $registration['cancelled_by_id']);
		}
		
		// Registration history
		$registration['history'] = $this->dao->queryAll(
				'SELECT h.*, pg.currency_code, p.first_name AS modified_by_first_name, p.last_name AS modified_by_last_name, ' .
				'b.begin_date, b.end_date, o.name AS organization_name, ' .
				'at_l10n.name AS accommodation_type_name, pt_l10n.name AS program_type_name ' .
				'FROM registration_history h ' .
				'JOIN registration r ON r.id = h.registration_id ' .
				'JOIN price_group pg ON pg.id = r.price_group_id ' .
				'JOIN person p ON p.id = h.modified_by_id ' .
				'LEFT OUTER JOIN batch b ON b.seminar_id = r.seminar_id AND b.batch_code = h.batch_code ' .
				'LEFT OUTER JOIN category c ON c.category_code = h.category_code AND c.seminar_id = r.seminar_id ' .
				'LEFT OUTER JOIN accomodation_type_l10n at_l10n ON at_l10n.accomodation_type_code = c.accomodation_type_code AND at_l10n.language_code = :lang ' .
				'LEFT OUTER JOIN program_type_l10n pt_l10n ON pt_l10n.program_type_code = c.program_type_code AND pt_l10n.language_code = :lang ' .
				'LEFT OUTER JOIN organization o ON o.id = h.payment_organization_id ' .
				'WHERE h.registration_id = :id ' .
				'ORDER BY h.version DESC',
				array('id' => $registrationId, 'lang' => $lang));
	
		return $registration;
	}
	
	/**
	 * Returns map with the following indices:
	 * registrationList, unconfirmedRegistrationList, cancelledRegistrationList,
	 * totalCashPriceByCurrency, totalBankPriceByCurrency,
	 * totalPriceByCurrency, cancellationFeesByCurrency
	 */
	public function getOrgAccountingData($seminarId, $organizationId) {
		$params = array('seminarId' => $seminarId, 'orgId' => $organizationId);
		$commonQueryPart = 'SELECT r.*, p.first_name, p.last_name, pg.name AS price_group_name, pg.currency_code, confirmedBy.first_name AS confirmed_by_first_name, confirmedBy.last_name AS confirmed_by_last_name, ' .
				'cancel_p.first_name AS cancelled_by_first_name, cancel_p.last_name AS cancelled_by_last_name ' . 
				'FROM registration r ' .
				'JOIN person p ON p.id = r.person_id ' .
				'LEFT OUTER JOIN person cancel_p ON cancel_p.id = r.cancelled_by_id ' .
				'JOIN price_group pg ON pg.id = r.price_group_id ' .
				'LEFT OUTER JOIN person confirmedBy ON confirmedBy.id = r.confirmed_by_id ' .
				'WHERE r.payment_organization_id = :orgId ' .
				'AND r.seminar_id = :seminarId ';
		
		$registrationList = $this->dao->queryAll(
				$commonQueryPart .
				'AND r.confirmed_at IS NOT NULL ' .
				'AND r.cancelled_at IS NULL ' .
				'ORDER BY r.id', $params);
		
		$unconfirmedRegistrationList = $this->dao->queryAll(
				$commonQueryPart .
				'AND r.confirmed_at IS NULL ' .
				'AND r.cancelled_at IS NULL ' .
				'ORDER BY r.id', $params);
		
		$cancelledRegistrationList = $this->dao->queryAll(
				$commonQueryPart .
				'AND r.cancelled_at IS NOT NULL ' .
				'ORDER BY r.id', $params);
		
		$totalPriceByCurrency = array();
		$totalCashPriceByCurrency = array();
		$totalBankPriceByCurrency = array();
		$cancellationFeesByCurrency = array();
		
		foreach ($registrationList as &$registration) {
			$currencyCode = $registration['currency_code'];
			$totalPriceByCurrency[$currencyCode] += intval($registration['total_price']);
			
			if ($cancelledRegistration['payment_type'] == 'cash') {
				$totalCashPriceByCurrency[$currencyCode] += $registration['total_price'];
			}
			else {
				$totalBankPriceByCurrency[$currencyCode] += $registration['total_price'];
			}
		}
		
		foreach ($cancelledRegistrationList as $cancelledRegistration) {
			if ($cancelledRegistration['cancellation_fee']) {
				$fee = intval($cancelledRegistration['cancellation_fee']);
				if ($fee) {
					$currencyCode = $cancelledRegistration['currency_code'];
					
					$totalPriceByCurrency[$currencyCode] += $fee;
					$cancellationFeesByCurrency[$currencyCode] += $fee;
					
					if ($cancelledRegistration['payment_type'] == 'cash') {
						$totalCashPriceByCurrency[$currencyCode] += $fee;
					}
					else {
						$totalBankPriceByCurrency[$currencyCode] += $fee;
					}
				}
			}
		}
		
		$paymentList = $this->dao->queryAll(
				'SELECT p.* ' .
				'FROM seminar_org_payment p ' .
				'WHERE p.seminar_id = :seminar_id ' .
				'AND p.organization_id = :org_id ' .
				'ORDER BY p.id',
				array('seminar_id' => $seminarId, 'org_id' => $organizationId));
		$totalPaymentsByCurrency = array();
		foreach ($paymentList as $payment) {
			if (! $totalPaymentsByCurrency[$payment['currency_code']]) {
				$totalPaymentsByCurrency[$payment['currency_code']] = 0;
			}
			$totalPaymentsByCurrency[$payment['currency_code']] += $payment['amount'];
		}
		
		// Payment vs price difference
		$usedCurrencyCodeList = array_unique(array_merge(array_keys($totalPaymentsByCurrency), array_keys($totalPriceByCurrency)));
		$paymentsMinusPricesByCurrency = array();
		foreach ($usedCurrencyCodeList as $currencyCode) {
			if ($totalPriceByCurrency[$currencyCode] != $totalPaymentsByCurrency[$currencyCode]) {
				$paymentsMinusPricesByCurrency[$currencyCode] = $totalPaymentsByCurrency[$currencyCode] - $totalPriceByCurrency[$currencyCode];
			}
		}
		
		$paymentAcceptance = $this->dao->getPaymentAcceptance($seminarId, $organizationId);
		if ($paymentAcceptance['confirmed_by_id']) {
			$paymentAcceptance['confirmed_by'] = $this->dao->getById('person', $paymentAcceptance['confirmed_by_id']);
		} 
		
		return array(
				'registrationList' => $registrationList,
				'unconfirmedRegistrationList' => $unconfirmedRegistrationList,
				'cancelledRegistrationList' => $cancelledRegistrationList,
				
				'totalPriceByCurrency' => $totalPriceByCurrency,
				'totalCashPriceByCurrency' => $totalCashPriceByCurrency,
				'totalBankPriceByCurrency' => $totalBankPriceByCurrency,
				'cancellationFeesByCurrency' => $cancellationFeesByCurrency,
				
				'paymentList' => $paymentList,
				'totalPaymentsByCurrency' => $totalPaymentsByCurrency,
				'paymentsMinusPricesByCurrency' => $paymentsMinusPricesByCurrency,
				'paymentAcceptance' => $paymentAcceptance);
	}
	
	public function computeRegistrationTotalPrice($registration) {
		$basicPrice = $this->dao->getCategoryPrice($registration['category_code'], $registration['price_group_id']);
		$socialDiscountHeight = 0;
		if ($registration['social_discount_applied']) {
			$socialDiscount = $this->dao->getSeminarSocialDiscount($registration['seminar_id']);
			$socialDiscountHeight = ($basicPrice * intval($socialDiscount['percentage'])) / 100;
		}
		$earlyDiscountHeight = 0;
		if ($registration['early_discount_applied']) {
			$earlyDiscount = $this->dao->getSeminarEarlyDiscount($registration['seminar_id']);
			$earlyDiscountHeight = ($basicPrice * intval($earlyDiscount['percentage'])) / 100;
		}
		$lateChargeHeight = 0;
		if ($registration['late_charge_applied']) {
			$lateCharge = $this->dao->getSeminarLateCharge($registration['seminar_id']);
			$lateChargeHeight = ($basicPrice * intval($earlyDiscount['percentage'])) / 100;
		}
		$totalPrice = $basicPrice - $socialDiscountHeight - $earlyDiscountHeight + $lateChargeHeight;
		if ($registration['extra_discount']) {
			$totalPrice -= $registration['extra_discount'];
		}
		if ($registration['payment_on_arrival_fee']) {
			$totalPrice += $registration['payment_on_arrival_fee'];
		}
		return $totalPrice;
	}
	
	/**
	 * @param array $registration should be loaded by SeminarService::loadRegistrationDisplayData()
	 */
	public function loadRegistrationEditFormData($registration) {
		$viewData = array();
		$viewData['registration'] = $registration;
		$batchList = $this->dao->queryAll(
				'SELECT b.batch_code, l10n.name, b.begin_date, b.end_date ' .
				'FROM batch b ' .
				'JOIN batch_l10n l10n ON l10n.batch_code = b.batch_code AND l10n.seminar_id = b.seminar_id AND l10n.language_code = :lang ' .
				'WHERE b.batch_type_code = :batch_type_code ' .
				'AND b.seminar_id = :seminar_id ' .
				'ORDER BY b.begin_date',
				array (
						'seminar_id' => $registration['seminar_id'],
						'batch_type_code' => $registration['batch']['batch_type_code'],
						'lang' => $this->getL10nLanguageCode()
				));
		$viewData['batchOptions'] = array();
		foreach ($batchList as $batch) {
			$viewData['batchOptions'][$batch['batch_code']] =
			$batch['batch_code'] . ' (' . $batch['name'] . ') ' .
			format::dateInterval($batch['begin_date'], $batch['end_date']);
		}
		 
		$priceGroup = $this->dao->getById('price_group', $registration['price_group_id']);
		$availableCategoryList = $this->dao->queryAll(
				'SELECT c.category_code, pl10n.name as program_type_name, al10n.name as accommodation_type_name, cp.price ' .
				'FROM category c ' .
				'JOIN category_price cp ON c.category_code = cp.category_code AND c.seminar_id = cp.seminar_id ' .
				'JOIN accomodation_type_l10n al10n ON al10n.accomodation_type_code = c.accomodation_type_code AND al10n.language_code = :lang ' .
				'JOIN program_type_l10n pl10n ON pl10n.program_type_code = c.program_type_code AND pl10n.language_code = :lang ' .
				'WHERE c.seminar_id = :seminar_id ' .
				'AND batch_type_code = :batch_type_code ' .
				'AND cp.price_group_id = :price_group_id ' .
				'ORDER BY pl10n.name, al10n.name ',
				array (
						'seminar_id' => $registration['seminar_id'],
						'batch_type_code' => $registration['batch']['batch_type_code'],
						'lang' => $this->getL10nLanguageCode(),
						'price_group_id' => $priceGroup['id']
				));
		$viewData['categoryOptions'] = array();
		foreach ($availableCategoryList as $category) {
			$viewData['categoryOptions'][$category['category_code']] =
			$category['program_type_name'] . ', ' . $category['accommodation_type_name'] . ' - ' .
			format::price($category['price'], $priceGroup['currency_code']);
		}
		 
		$seminarAndLang = array (
				'seminar_id' => $registration['seminar_id'],
				'lang' => $this->getL10nLanguageCode());
		 
		$viewData['karmayogaOptions'] = $this->dao->queryOptions(
				'SELECT ak.karmayoga_type_code, l10n.name ' .
				'FROM available_karmayoga_type ak ' .
				'JOIN karmayoga_type_l10n l10n ON l10n.karmayoga_type_code = ak.karmayoga_type_code AND l10n.language_code = :lang ' .
				'WHERE ak.seminar_id = :seminar_id ' .
				'ORDER BY l10n.name',
				$seminarAndLang);
		 
		$viewData['exerciseGroupOptions'] = $this->dao->queryOptions(
				'SELECT aeg.exercise_group_code, l10n.name ' .
				'FROM available_exercise_group aeg ' .
				'JOIN exercise_group_l10n l10n ON l10n.exercise_group_code = aeg.exercise_group_code AND l10n.language_code = :lang ' .
				'AND aeg.seminar_id = :seminar_id ' .
				'ORDER BY l10n.name',
				$seminarAndLang);
		
		$viewData['paymentAcceptance'] = $this->dao->getPaymentAcceptance($registration['seminar_id'], $registration['payment_organization_id']);
		
		return $viewData;
	}
	
	/**
	 * Saves edited registration.
	 * 
	 * @param array $registration Original values of the registration being edited.
	 * @param array $formData Data from the editation form.
	 * @param string $userRole Role of the user who edited the registration. Either UserRole::$admin or UserRole::$org.
	 * @throws Exception
	 */
	public function saveEditedRegistration($registration, $formData, $userRole) {
		$batch = $this->dao->getBatch($registration['seminar_id'], $formData['batch_code']);
		$category = $this->dao->getCategory($registration['seminar_id'], $formData['category_code']);
		$priceGroup = $this->dao->getById('price_group', $registration['price_group_id']);
		$seminarSocialDiscount = $this->dao->getSeminarSocialDiscount($registration['seminar_id']);
		$seminarEarlyDiscount = $this->dao->getSeminarEarlyDiscount($registration['seminar_id']);
		$seminarLateCharge = $this->dao->getSeminarEarlyDiscount($registration['seminar_id']);
		$oldCategory = $this->dao->getCategory($registration['seminar_id'], $registration['category_code']);
		if (! $batch || ! $category || $category['batch_type_code'] != $oldCategory['batch_type_code']) {
			throw InvalidArgumentException(text::get('error-invalidId'));
		}
		
		// Create the history data - write only fields that were changed
		$history = array (
				'registration_id' => $registration['id'],
				'version' => $registration[sql::$ENTITY_VERSION_KEY]);
		$historyFields = array('batch_code', 'category_code', 'social_discount_applied', 'price_paid');
		if ($seminarSocialDiscount) {
			$historyFields[] = 'social_discount_applied';
		}
		if ($userRole == UserRole::$admin) {
			$historyFields[] = 'extra_discount';
			$historyFields[] = 'extra_discount_reason';
			if ($seminarEarlyDiscount) {
				$historyFields[] = 'early_discount_applied';
			}
			if ($seminarLateCharge) {
				$historyFields[] = 'late_charge_applied';
			}
		}
		
		
		$historyNeedsToBeWriten = false;
		foreach ($historyFields as $field) {
			if ($formData[$field] != $registration[$field]) {
				$historyNeedsToBeWriten = true;
				$history[$field] = $formData[$field];
				$registration[$field] = $formData[$field];
			}
		}
		
		// Update registration and write replaced values to history
		$registrationWasUpdated = false;
		$this->dao->getDb()->beginTransaction();
		try {
			$oldPrice = $registration['total_price'];
			$registration['total_price'] = $this->computeRegistrationTotalPrice($registration);
			if ($oldPrice != $registration['total_price']) {
				$history['total_price'] = $oldPrice;
			}
			if ($historyNeedsToBeWriten) {
				$historyEntity = new RegistrationHistory_Model($history);
				$this->dao->insertEntity($historyEntity);
			}
			 
			if ($historyNeedsToBeWriten ||
					$registration['exercise_group_code'] != $formData['exercise_group_code'] ||
					$registration['preferred_karmayoga_type_code'] != $formData['preferred_karmayoga_type_code']) {
		
				$registration['exercise_group_code'] = $formData['exercise_group_code'];
				$registration['preferred_karmayoga_type_code'] = $formData['preferred_karmayoga_type_code'];
		
				$this->dao->update('registration', $registration);
				$this->dao->getDb()->commit();
				 
				if ($oldPrice != $registration['total_price']) {
					$this->msg->warning('registration-update-priceChanged',
							format::price($oldPrice, $priceGroup['currency_code']),
							format::price($registration['total_price'], $priceGroup['currency_code']));
				}
				$this->msg->info('registration-update-success');
				$registrationWasUpdated = true;
			}
			else {
				$this->msg->info('registration-update-noChanges');
			}
		}
		catch (Exception $e) {
			// If something raised an exception in our transaction block, roll it back.
			$this->dao->getDb()->rollBack();
		
			// If there are some recoverable cases, handle them here.
		
			throw $e;
		}
		
		// Send an email to the user about the change made
		$person = $this->dao->getById('person', $registration['person_id']);
		if ($registrationWasUpdated && $person['email']) {
			$seminar = $this->dao->fetchSeminar($registration['seminar_id'], $this->getL10nLanguageCode());
			$organization = $this->dao->getOrganization($registration['payment_organization_id']);
			$extraInfo = '';
			if ($oldPrice != $registration['total_price']) {
				$extraInfo = "\n" . text::get(
						'registration-updated-emailToUser-priceChanged',
						format::price($oldPrice, $priceGroup['currency_code']),
						format::price($registration['total_price'], $priceGroup['currency_code'])) . "\n";
			}
			
			if ($userRole == UserRole::$org) {
				$byWho = text::get('registration-updated-byOrg', $organization['name']);
			}
			else if ($userRole == UserRole::$admin) {
				$byWho = text::get('registration-updated-byAdmin');
			} 
			$emailBody = text::get('registration-updated-emailToUser-body',
					$registration['id'],
					$seminar['name'],
					$byWho,
					$extraInfo);
			$mailResult = email::sendInUtf8(
					format::personFullName($person),
					$person['email'],
					text::get('registration-updated-emailToUser-subject', $registration['id']),
					email::addHeadingAndClosing($emailBody, $person));
			if ($mailResult == email::$SUCCESS) {
				// Tell the admin/organizer, that the registration was updated.
				$this->msg->info('registration-updated-emailToUser-sent');
			}
		}
	}
}
